package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
)

type InfluxDBData struct {
	Series []InfluxDBSeries `json:"series,omitempty"`
}

type InfluxDBSeries struct {
	Name    string            `json:"name,omitempty"`
	Tags    map[string]string `json:"tags,omitempty"`
	Columns []string          `json:"columns,omitempty"`
	Values  [][]interface{}   `json:"values,omitempty"`
}

func unmarshal(data []byte) (*InfluxDBData, error) {
	s := InfluxDBData{}
	err := json.Unmarshal(data, &s)
	if err != nil {
		return nil, err
	}
	return &s, nil
}

type cacheType struct {
	m map[string]interface{}
}

func (c *cacheType) Parse(s *InfluxDBData) error {
	if s == nil {
		return errors.New("nil data")
	}
	for _, series := range s.Series {
		err := c.ParseSeries(&series)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *cacheType) ParseSeries(s *InfluxDBSeries) error {
	switch s.Name {
	case "NMEA2000_PositionRapidUpdate":
		{
			return c.formatPosistionRapidUpdate(s)
		}
	case "NMEA2000_EngineParametersRapidUpdate":
		{
			return c.formatEngineParametersRapidUpdate(s)
		}
	case "NMEA2000_EngineParametersDynamic":
		{
			return c.formatEngineParametersDynamic(s)
		}
	case "NMEA2000_WindData":
		{
			return c.formatWindData(s)
		}
	case "NMEA2000_GeneratorAverageBasicACQuantities":
		{
			return c.formatGeneratorAverageBasicACQuantities(s)
		}
	default:
		{
			return errors.New("not implemented")
		}
	}
}

func (c *cacheType) formatPosistionRapidUpdate(s *InfluxDBSeries) error {
	setField(&c.m, s.Columns, s.Values, "time", "time")
	setField(&c.m, s.Columns, s.Values, "longitude", "longitude")
	setField(&c.m, s.Columns, s.Values, "latitude", "latitude")
	return nil
}

func (c *cacheType) formatEngineParametersRapidUpdate(s *InfluxDBSeries) error {
	engineIndex, err := mapEngineIndexByInstance(s)
	if err != nil {
		return err
	}
	setField(&c.m, s.Columns, s.Values, "Speed", withEngineIndex(engineIndex, "REV"))
	return nil
}

func (c *cacheType) formatEngineParametersDynamic(s *InfluxDBSeries) error {
	engineIndex, err := mapEngineIndexByInstance(s)
	if err != nil {
		return err
	}
	setField(&c.m, s.Columns, s.Values, "time", "time")
	setField(&c.m, s.Columns, s.Values, "AlternatorPotential", withEngineIndex(engineIndex, "MEAP"))
	setField(&c.m, s.Columns, s.Values, "FuelRate", withEngineIndex(engineIndex, "MEFR"))
	setField(&c.m, s.Columns, s.Values, "OilPressure", withEngineIndex(engineIndex, "MEOIP"))
	setField(&c.m, s.Columns, s.Values, "OilTemperature", withEngineIndex(engineIndex, "MEOT"))
	setField(&c.m, s.Columns, s.Values, "PercentEngineLoad", withEngineIndex(engineIndex, "MPEL"))
	setField(&c.m, s.Columns, s.Values, "PercentEngineTorque", withEngineIndex(engineIndex, "MEPET"))
	setField(&c.m, s.Columns, s.Values, "Temperature", withEngineIndex(engineIndex, "MEWOT"))
	setFieldFloat(&c.m, s.Columns, s.Values, "TotalEngineHours", withEngineIndex(engineIndex, "ACOT"), 1.0/3600.0)

	return nil
}

func (c *cacheType) formatWindData(s *InfluxDBSeries) error {
	setField(&c.m, s.Columns, s.Values, "time", "time")
	setField(&c.m, s.Columns, s.Values, "WindSpeed", "WindSPD")
	setField(&c.m, s.Columns, s.Values, "WindAngle", "WindAngle")
	return nil
}

func (c *cacheType) formatGeneratorAverageBasicACQuantities(s *InfluxDBSeries) error {
	setField(&c.m, s.Columns, s.Values, "time", "time")
	setField(&c.m, s.Columns, s.Values, "ACFrequncy", "ACFrequency")
	setField(&c.m, s.Columns, s.Values, "ACRMSVoltage", "ACRMSVoltage")
	return nil
}

func setField(m *map[string]interface{}, columns []string, values [][]interface{}, name string, setName string) {
	idx, err := getIndex(columns, name)
	if err != nil {
		return
	}
	if len(values) < 1 {
		return
	}
	value := values[len(values)-1]
	if idx < 0 || idx >= len(value) {
		return
	}
	v := value[idx]
	(*m)[setName] = v
}

func setFieldFloat(m *map[string]interface{}, columns []string, values [][]interface{}, name string, setName string, scale float64) {
	idx, err := getIndex(columns, name)
	if err != nil {
		return
	}
	if len(values) < 1 {
		return
	}
	value := values[len(values)-1]
	if idx < 0 || idx >= len(value) {
		return
	}
	v := value[idx]
	var fv float64
	switch t := v.(type) {
	case int:
		{
			fv = float64(t)
			break
		}
	case int8:
		{
			fv = float64(t)
			break
		}
	case int16:
		{
			fv = float64(t)
			break
		}
	case int32:
		{
			fv = float64(t)
			break
		}
	case float32:
		{
			fv = float64(t)
			break
		}
	case int64:
		{
			fv = float64(t)
			break
		}
	case uint:
		{
			fv = float64(t)
			break
		}
	case uint8:
		{
			fv = float64(t)
			break
		}
	case uint16:
		{
			fv = float64(t)
			break
		}
	case uint32:
		{
			fv = float64(t)
			break
		}
	case uint64:
		{
			fv = float64(t)
			break
		}
	case float64:
		{
			fv = t
			break
		}
	case string:
		{
			x, err := strconv.ParseFloat(t, 64)
			if err != nil {
				break
			}
			fv = x
		}
	default:
		{
			break
		}
	}
	fv = fv * scale
	(*m)[setName] = fv
}

func withEngineIndex(index int, name string) string {
	return fmt.Sprintf("ME%d_%s", index, name)
}

var engineMaps = map[string]int{
	"17":  1,
	"236": 1,
	"237": 2,
}

var engineInstanceMaps = map[string]int{
	"Single Engine or Dual Engine Port": 1,
	"Dual Engine Starboard":             2,
}

func mapEngineIndexBySrc(s *InfluxDBSeries) (int, error) {
	var src string
	var ok bool
	if src, ok = s.Tags["src"]; ok {
		if idx, ok := engineMaps[src]; ok {
			return idx, nil
		}
		return -1, fmt.Errorf("unknown engine src: %s", src)
	}
	return -1, fmt.Errorf("cannot get engine src")
}

func mapEngineIndexByInstance(s *InfluxDBSeries) (int, error) {
	var instance string
	var ok bool
	if instance, ok = s.Tags["fields_Instance"]; ok {
		if idx, ok := engineInstanceMaps[instance]; ok {
			return idx, nil
		}
		return -1, fmt.Errorf("unknown engine instance: %s\n", instance)
	}
	return -1, fmt.Errorf("cannot get engine instance")
}

func getIndex(s []string, name string) (int, error) {
	for idx, v := range s {
		if v == name {
			return idx, nil
		}
	}
	return -1, fmt.Errorf("name %s not exists", name)
}

func getValue(value []interface{}, idx int) (interface{}, error) {
	if idx < 0 || idx >= len(value) {
		return nil, fmt.Errorf("index out of range: %d", idx)
	}
	return value[idx], nil
}
